﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using NodeBasedSkillEditor.ViewModel;

namespace NodeBasedSkillEditor.View
{
    public class GridCanvas : Canvas
    {
        public const float DefaultGridSpace = 100;
        public const float DefaultSubGridSpace = DefaultGridSpace / 4;

        const float _gridCrossRadiusFactor = 1 / 12f;
        const float _gridCrossIntervalFactor = 1 - _gridCrossRadiusFactor;
        const float _gridCrossOffestFactor = _gridCrossRadiusFactor * 0.5f;

        const float _crossDashSegment0 = DefaultGridSpace * _gridCrossRadiusFactor;
        const float _crossDashSegment1 = DefaultGridSpace * _gridCrossIntervalFactor;
        const float _crossDashOffset = DefaultGridSpace * _gridCrossOffestFactor;

        public bool IsDrawingGrid {
            get { return (bool)GetValue(IsDrawingGridProperty); }
            set { SetValue(IsDrawingGridProperty, value); }
        }
        public static readonly DependencyProperty IsDrawingGridProperty =
            DependencyProperty.Register("IsDrawingGrid", typeof(bool), typeof(GridCanvas),
                new PropertyMetadata(true, OnIsDrawingGridPropertyChanged));

        public float Zoom {
            get { return (float)GetValue(ZoomProperty); }
            set { SetValue(ZoomProperty, Math.Clamp(value, VM_DAG.MinZoom, VM_DAG.MaxZoom)); }
        }
        public static readonly DependencyProperty ZoomProperty =
            DependencyProperty.Register("Zoom", typeof(float), typeof(GridCanvas),
                new PropertyMetadata(1f, OnZoomPropertyChanged));

        public Vector Pan {
            get { return (Vector)GetValue(PanProperty); }
            set { SetValue(PanProperty, value); }
        }

        public static readonly DependencyProperty PanProperty =
            DependencyProperty.Register("Pan", typeof(Vector), typeof(GridCanvas),
                new PropertyMetadata(new Vector(), OnPanPropertyChanged));

        private readonly Brush _subGridBrush = new SolidColorBrush(DAGHelper.DAGSubGridColor);
        private readonly Brush _mainGridBrush = new SolidColorBrush(DAGHelper.DAGMainGridColor);
        private readonly Brush _crossBrush = new SolidColorBrush(DAGHelper.DAGGridCrossColor);
        private readonly Pen _subGrid_Pen = new();
        private readonly Pen _mainGrid_pen = new();
        private readonly Pen _cross_pen = new();

        public Vector CanvasOffsetWithZoom { get { return Pan * Zoom; } }

        public float CurrentGridSpace { get { return Zoom * DefaultGridSpace * _gridSpaceFactor; } }

        float _gridSpaceFactor = 1;

        static GridCanvas()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(GridCanvas), new FrameworkPropertyMetadata(typeof(GridCanvas)));
        }

        public GridCanvas()
        {
        }

        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);
            _subGridBrush.Freeze();
            _subGrid_Pen.Thickness = 1;
            _subGrid_Pen.Brush = _subGridBrush;
            _subGrid_Pen.Freeze();

            _mainGridBrush.Freeze();
            _mainGrid_pen.Thickness = 1;
            _mainGrid_pen.Brush = _mainGridBrush;
            _subGrid_Pen.Freeze();

            _crossBrush.Freeze();
            _cross_pen.Thickness = 1;
            _cross_pen.Brush = _crossBrush;
            _cross_pen.DashStyle = new DashStyle(new double[] { _crossDashSegment0, _crossDashSegment1 }, _crossDashOffset);
            //_cross_pen.Freeze();
        }

        public void ZoomByMouseWheel(MouseWheelEventArgs e)
        {
            // 相似变换
            var mousePos = e.GetPosition(this);
            var prevOffsetAboutMouse = (Point)CanvasOffsetWithZoom - mousePos;
            var originOffsetAboutMouse = prevOffsetAboutMouse / Zoom;
            Zoom *= e.Delta > 0 ? 1/VM_DAG.ZoomFactor : VM_DAG.ZoomFactor;
            var newOriginPosAboutMouse = originOffsetAboutMouse * Zoom;
            var newOffset = (Vector)(newOriginPosAboutMouse + mousePos);
            Pan = newOffset / Zoom;

            InvalidateVisual();
        }

        public void PanByMouseMove(Vector mouseMoveInDag)
        {
            Pan += mouseMoveInDag / Zoom;
        }

        public void ResetPan()
        {
            Pan = new Vector();
        }

        static void OnIsDrawingGridPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var s = sender as GridCanvas;
            s.InvalidateVisual();
        }

        void OnZoomChanged()
        {
            UpdateSpaceFactor();

            InvalidateVisual();
        }

        static void OnZoomPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var s = sender as GridCanvas;
            s.OnZoomChanged();
        }

        void OnPanChanged()
        {
            InvalidateVisual();
        }

        static void OnPanPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var s = sender as GridCanvas;
            s.OnPanChanged();
        }

        void UpdateSpaceFactor()
        {
            var invZoom = 1 / Zoom;

            _gridSpaceFactor = 1;
            for (int i = 0; i < 30; i++)
            {
                var nextFactor = _gridSpaceFactor * 2;
                if (invZoom < nextFactor)
                {
                    break;
                }
                else
                {
                    _gridSpaceFactor *= 2;
                }
            }

            _cross_pen.DashStyle.Dashes[0] = CurrentGridSpace * _gridCrossRadiusFactor;
            _cross_pen.DashStyle.Dashes[1] = CurrentGridSpace * _gridCrossIntervalFactor;
            _cross_pen.DashStyle.Offset = CurrentGridSpace * _gridCrossOffestFactor;
        }

        protected override void OnRender(DrawingContext dc)
        {
            base.OnRender(dc);
            DrawGrid(dc);
        }

        void DrawGrid(DrawingContext dc)
        {
            if (ActualHeight * ActualWidth != 0 && IsDrawingGrid)
            {
                var offsetModulo = CurrentGridSpace;

                var subGridStep = offsetModulo * 0.25f;
                var y = CanvasOffsetWithZoom.Y % offsetModulo - offsetModulo;
                var lineIndex = 0;
                while (y < ActualHeight)
                {
                    var start = new Point(0, y);
                    var end = new Point(ActualWidth, y);
                    if (lineIndex % 4 == 0)
                    {
                        dc.DrawLine(_mainGrid_pen, start, end);
                    }
                    else
                    {
                        dc.DrawLine(_subGrid_Pen, start, end);
                    }
                    y += subGridStep;
                    lineIndex++;
                }

                var x = CanvasOffsetWithZoom.X % offsetModulo - offsetModulo;
                lineIndex = 0;
                while (x < ActualWidth)
                {
                    var start = new Point(x, 0);
                    var end = new Point(x, ActualHeight);
                    if (lineIndex % 4 == 0)
                    {
                        dc.DrawLine(_mainGrid_pen, start, end);
                    }
                    else
                    {
                        dc.DrawLine(_subGrid_Pen, start, end);
                    }
                    x += subGridStep;
                    lineIndex++;
                }

                //draw cross hori-lines
                var crossStep = offsetModulo;
                x = CanvasOffsetWithZoom.X % offsetModulo - offsetModulo;
                y = CanvasOffsetWithZoom.Y % offsetModulo - offsetModulo;
                while (y < ActualHeight + offsetModulo)
                {
                    var start = new Point(x, y);
                    var end = new Point(ActualWidth, y);
                    dc.DrawLine(_cross_pen, start, end);
                    y += crossStep;
                }

                //draw cross vert-lines
                x = CanvasOffsetWithZoom.X % offsetModulo - offsetModulo;
                y = CanvasOffsetWithZoom.Y % offsetModulo - offsetModulo;
                while (x < ActualWidth + offsetModulo)
                {
                    var start = new Point(x, y);
                    var end = new Point(x, ActualHeight);
                    dc.DrawLine(_cross_pen, start, end);
                    x += crossStep;
                }
            }
        }

    }
}
